package container.map;

import container.Constants;

import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;

/**
 * https://blog.csdn.net/f641385712/article/details/81147941
 * https://tech.meituan.com/2016/06/24/java-hashmap.html
 */
public class T06_ConcurrentHashMap {

    static Map<UUID, UUID> map = new ConcurrentHashMap<>();

    static int count = Constants.COUNT;
    static UUID[] keys = new UUID[count];
    static UUID[] values = new UUID[count];
    static final int THREAD_COUNT = Constants.THREAD_COUNT;

    static {
        for (int i = 0; i < count; i++) {
            keys[i] = UUID.randomUUID();
            values[i] = UUID.randomUUID();
        }
    }

    static class MyThread extends Thread {
        int start;
        int gap = count / THREAD_COUNT;

        public MyThread(int start) {
            this.start = start;
        }

        @Override
        public void run() {
            for (int i = start; i < start + gap; i++) {
                map.put(keys[i], values[i]);
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        long start = System.currentTimeMillis();
        Thread[] threads = new Thread[THREAD_COUNT];
        for (int i = 0; i < threads.length; i++) {
            threads[i] = new MyThread(i * (count / THREAD_COUNT));
            threads[i].start();
            threads[i].join();
        }
        long end = System.currentTimeMillis();
        System.out.println("time: " + (end - start));
        System.out.println("size: " + map.size());

        //-----------------------------------

        start = System.currentTimeMillis();
        for (int i = 0; i < threads.length; i++) {
            threads[i] = new Thread(() -> {
                for (int j = 0; j < 10000000; j++) {
                    map.get(keys[10]);
                }
            });
            threads[i].start();
            threads[i].join();
        }
        end = System.currentTimeMillis();
        System.out.println("time: " + (end - start));
        System.out.println("size: " + map.size());
    }

    /**
     * Map 的遍历问题，选择哪种方式性能最优？
     * Java8 之后提供了 lambda 表达式的遍历方式。因此如果可以用 lambda 表达式遍历，那就毫无疑问的直接选择即可。
     * 如果你是遍历 HashMap：
     * * 遍历既需要 key 也需要 value 的时候：keySet 与 entrySet 方法的性能差异取决于 key 的具体情况，如复杂度（复杂对象）、离散度、冲突率等。
     * * 换言之，取决于 HashMap 查找 value 的开销。entrySet 一次性取出所有 key 和 value 的操作是有性能开销的，当这个损失小于 HashMap 查
     * * 找 value 的开销时，entrySet 的性能优势就会体现出来。例如下述对比测试中，当 key 是最简单的数值字符串时，keySet 可能反而会更高效，耗
     * * 时比 entrySet 少 10%。总体来说还是推荐使用 entrySet。因为当 key 很简单时，其性能或许会略低于 keySet，但却是可控的；而随着 key 的
     * * 复杂化，entrySet 的优势将会明显体现出来。
     * * 遍历只需要 key 的时候：keySet方法更为合适，因为entrySet将无用的value也给取出来了，浪费了性能和空间
     * * 只遍历 value 时，使用 vlaues 方法是最佳选择
     * 如果你是遍历 TreeMap：
     * * 同时遍历 key 和 value 时，与 HashMap 不同，entrySet 的性能远远高于 keySet。这是由 TreeMap 的查询效率决定的，也就是说，TreeMap 查
     * * 找 value 的开销较大，明显高于 entrySet 一次性取出所有 key 和 value 的开销。因此，遍历 TreeMap 时强烈推荐使用 entrySet 方法。
     * * 只遍历 key 时，keySet 方法更为合适，因为 entrySet 将无用的 value 也给取出来了，浪费了性能和空间
     * * 只遍历 value 时，使用 vlaues 方法是最佳选择
     * 综上：lambda遍历是首选。当lambda不适用（比如一边遍历一边需要移除等等），entrySet的遍历方式是最优的方式选择。
     */
    public static void traverse() {
        Map<String, String> traverseMap = new ConcurrentHashMap<>();
        // entrySet 方式：key 和 value一次性都拿出来
        for (Map.Entry<String, String> entry : traverseMap.entrySet()) {
            String key = entry.getKey();
            String value = entry.getValue();
        }
        // keySet 方式：先拿出 key，再去拿 value
        for (String key : traverseMap.keySet()) {
            String value = traverseMap.get(key);
        }
        // values 方式：当只需要 value 的时候，这种方式才合适
        for (String value : traverseMap.values()) {
            System.out.println(value);
        }
    }
}
